package cn.admin.configcenter.configservice.persist;

import cn.admin.configcenter.configservice.domain.ConfigInfo;
import cn.admin.configcenter.configservice.service.CacheRepositoryManager;
import cn.admin.configcenter.configservice.util.ConfigKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.CannotGetJdbcConnectionException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.PostConstruct;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.concurrent.ConcurrentHashMap;

@Repository("derbyRepository")
public class ConfigBaseDAODerbyImpl implements ConfigBaseDAO {

    private static final Logger LOG =
            LoggerFactory.getLogger(ConfigBaseDAODerbyImpl.class);

    @Autowired
    protected JdbcTemplate jdbcTemplate;

    @Autowired
    private TransactionTemplate txTemplate;

    @Autowired
    private CacheRepositoryManager cacheRepository;

    private static final ConfigInfoRowMapper
            CONFIG_INFO_ROW_MAPPER = new ConfigInfoRowMapper();

    public ConfigBaseDAODerbyImpl() {
        LOG.info(ConfigBaseDAODerbyImpl.class.getName() + ".<init>");
    }

    @PostConstruct
    public void initialize() {
        LOG.info("loading CacheRepositoryManager ...");
        ConcurrentHashMap<String,String> cacheMap = this.loadCacheRepository();
        this.cacheRepository.setCacheMap(cacheMap);
        LOG.info("CacheRepositoryManager loaded");
        this.txTemplate.setTimeout(30);
    }

    private ConcurrentHashMap<String,String> loadCacheRepository() {

        return this.jdbcTemplate.query(
                "SELECT data_id, group_id, src_ip, md5 FROM config_info",
                rs -> {
                    ConcurrentHashMap<String,String> resultMap =
                            new ConcurrentHashMap<>();

                    while(rs.next()) {
                        String configKey = ConfigKey.getKey(
                                rs.getString("data_id"),
                                rs.getString("group_id"));
                        String md5 = rs.getString("md5");
                        if (md5 != null) {
                            resultMap.put(configKey, md5);
                        }
                    }

                    return resultMap;
                });
    }


    @Override
    public void createConfig(ConfigInfo config) {
        Timestamp createTime = new Timestamp(System.currentTimeMillis());
        this.jdbcTemplate.update(
                "insert into config_info(data_id, group_id, content, md5, " +
                        "src_ip, gmt_create, gmt_modified, app_name) " +
                        "values (?,?,?,?,?,?,?,?)",
                config.getDataId(), config.getGroup(), config.getContent(),
                config.getMd5(), config.getSrcIp(),
                createTime, createTime, config.getAppName());
        this.cacheRepository.refreshCache(ConfigKey.getKey(config.getDataId(),
                config.getGroup()), config.getMd5());

    }

    @Override
    public void removeConfig(ConfigInfo config) {
        this.jdbcTemplate.update("delete from config_info where data_id=? " +
                "and group_id=?",
                config.getDataId(), config.getGroup());
        this.cacheRepository.removeCache(ConfigKey.getKey(config.getDataId(),
                config.getGroup()));
    }

    @Override
    public ConfigInfo getConfig(ConfigInfo config) {
        return this.jdbcTemplate.
                queryForObject(
                        "select * from config_info where data_id=? " +
                                "and group_id=?",
                        new Object[]{config.getDataId(), config.getGroup()},
                        CONFIG_INFO_ROW_MAPPER);
    }

    @Override
    public ConfigInfo getConfigByDataIdAndGroupAndSrcIp(String dataId,
                                                        String group,
                                                        String srcIp) {
        return this.jdbcTemplate.queryForObject(
                "select * from config_info where data_id=? " +
                        "and group_id=? and src_ip=?",
                new Object[]{dataId, group, srcIp}, CONFIG_INFO_ROW_MAPPER);
    }

    @Override
    public boolean createOrUpdateConfigByDataIdAndGroupAndSrcIp(
            ConfigInfo config) {
        return this.insertOrUpdateConfigByDataIdAndGroupAndSrcIp(config);
    }

    @Override
    public void updateConfig(ConfigInfo config) {
        Timestamp modifiedTime = new Timestamp(System.currentTimeMillis());
        this.jdbcTemplate.update(
                "update config_info set content=?, md5=?, src_ip=?, " +
                        "gmt_modified=?, app_name=? where data_id=? " +
                        "and group_id=?",
                config.getContent(), config.getMd5(),
                config.getSrcIp(), modifiedTime,
                config.getAppName(), config.getDataId(), config.getGroup());
        this.cacheRepository.refreshCache(ConfigKey.getKey(config.getDataId(), config.getGroup()),
                config.getMd5());

    }

    @Override
    public void silentUpdateConfig(ConfigInfo config) {
        Timestamp modifiedTime = new Timestamp(System.currentTimeMillis());
        this.jdbcTemplate.update("update config_info set content=?, md5=?, src_ip=?," +
                        " gmt_modified=?, app_name=? where data_id=? and group_id=?",
                config.getContent(), config.getMd5(), config.getSrcIp(),
                modifiedTime, config.getAppName(),
                config.getDataId(), config.getGroup());
    }

    @Override
    public void updateConfigByDataIdAndGroupAndSrcIp(ConfigInfo config) {
        Timestamp modifiedTime = new Timestamp(System.currentTimeMillis());
        this.jdbcTemplate.update("update config_info set content=?, md5=?, gmt_modified=?, app_name=? " +
                "where data_id=? and group_id=? and src_ip=?",
                config.getContent(), config.getMd5(),
                modifiedTime, config.getAppName(),
                config.getDataId(), config.getGroup(), config.getSrcIp());
        this.cacheRepository.refreshCache(ConfigKey.getKey(config.getDataId(),
                config.getGroup()), config.getMd5());
    }

    @Override
    public boolean createOrUpdateConfig(ConfigInfo config) {
        return this.insertOrUpdateWithTranscation(config);
    }

    public boolean insertOrUpdateConfigByDataIdAndGroupAndSrcIp(
            final ConfigInfo config) {
        Boolean result = this.txTemplate.execute(transactionStatus -> {
            try {
                if (ConfigBaseDAODerbyImpl.this.existsByDataIdAndGroupAndSrcIp(config)) {
                    ConfigBaseDAODerbyImpl.this.updateConfigByDataIdAndGroupAndSrcIp(config);
                } else {
                    ConfigBaseDAODerbyImpl.this.createConfig(config);
                }
            } catch (CannotGetJdbcConnectionException e) {
                LOG.error("[db-error] " + e.toString(),e);
                throw e;
            }
            return true;
        });
        return result != null && result;
    }

    public boolean existsByDataIdAndGroupAndSrcIp (ConfigInfo config) {
        Integer count = this.jdbcTemplate.queryForObject(
                "select count(id) " +
                "from config_info where data_id=? and group_id=? and src_ip=?",
                new Object[]{config.getDataId(), config.getGroup(),
                        config.getSrcIp()}, Integer.class);
        return count != null && count > 0;
    }

    public boolean exists(ConfigInfo config) {
        Integer count = this.jdbcTemplate.queryForObject(
                "select count(id) from config_info where " +
                        "data_id=? and group_id=?",
                new Object[]{config.getDataId(), config.getGroup()}, Integer.class);
        return count != null && count > 0;
    }

    public boolean insertOrUpdateWithTranscation(final ConfigInfo config) {
        Boolean result = this.txTemplate.execute(transactionStatus -> {
            try {
                if (ConfigBaseDAODerbyImpl.this.exists(config)) {
                    ConfigBaseDAODerbyImpl.this.updateConfig(config);
                } else {
                    ConfigBaseDAODerbyImpl.this.createConfig(config);
                }
            } catch (CannotGetJdbcConnectionException e) {
                LOG.error("[db-error] " + e.toString(),e);
                throw e;
            }
            return true;
        });
        return result != null && result;
    }

    static final class ConfigInfoRowMapper implements RowMapper<ConfigInfo> {


        @Override
        public ConfigInfo mapRow(ResultSet rs, int i) throws SQLException {
            ConfigInfo config = new ConfigInfo();
            config.setId(rs.getLong("id"));
            config.setDataId(rs.getString("data_id"));
            config.setGroup(rs.getString("group_id"));
            config.setContent(rs.getString("content"));
            config.setMd5(rs.getString("md5"));
            config.setSrcIp(rs.getString("src_ip"));
            config.setCreateTimestamp(rs.getTimestamp("gmt_create").getTime());
            config.setLastModifiedTimestamp(rs.getTimestamp("gmt_modified").getTime());
            config.setAppName(rs.getString("app_name"));
            return config;
        }
    }
}
